#pragma once

#include <cfloat>
#include <cmath>


inline bool isEqual(float a, float b)
{
    return std::fabs(a - b) < FLT_EPSILON;
}


class Vec2
{
public:
    union
    {
        float data[2] = { 0 };
        struct
        {
            float x, y;
        };
        struct
        {
            float r, g;
        };
    };

    Vec2() : x(0.f), y(0.f) {}
    Vec2(float _x, float _y) : x(_x), y(_y) {}
    explicit Vec2(float f) : x(f), y(f) {}
    Vec2(const Vec2&) = default;


    inline void set(float _x, float _y)
    {
        this->x = _x;
        this->y = _y;
    }

    inline void set(const Vec2& v)
    {
        this->x = v.x;
        this->y = v.y;
    }

    inline void add(float _x, float _y)
    {
        this->x += _x;
        this->y += _y;
    }

    inline void add(const Vec2& v)
    {
        this->x += v.x;
        this->y += v.y;
    }

    inline void subtract(const Vec2& v)
    {
        this->x -= v.x;
        this->y -= v.y;
    }

    inline void negate()
    {
        this->x = -this->x;
        this->y = -this->y;
    }

    inline void scale(float scalar)
    {
        this->x *= scalar;
        this->y *= scalar;
    }

    inline void scale(const Vec2& scale)
    {
        this->x *= scale.x;
        this->y *= scale.y;
    }

    void normalize();
    Vec2 getNormalized() const;

    // counterclockwise, uint is radian
    void rotate(float angle);
    float distance(const Vec2& v) const;
    float distanceSquared(const Vec2& v) const;

    inline float dot(const Vec2& v) const
    {
        return x * v.x + y * v.y;
    }

    inline float cross(const Vec2& v) const
    {
        return x * v.y - y * v.x;
    }

    inline float length() const
    {
        return std::sqrtf(x * x + y * y);
    }

    inline void withLength(float len)
    {
        auto factor = length() / len;
        *this /= factor;
    }

    inline float lengthSquared() const
    {
        return x * x + y * y;
    }

    inline bool isZero() const
    {
        return isEqual(x, 0.f) && isEqual(y, 0.f);
    }

    inline bool equals(const Vec2& v) const
    {
        return isEqual(x, v.x) && isEqual(y, v.y);
    }

    /// return is radian
    float angle(const Vec2& v) const;

    inline Vec2& operator=(const Vec2& v) = default;
    inline Vec2 operator+(const Vec2& v) const
    {
        return { x + v.x, y + v.y };
    }

    inline Vec2& operator+=(const Vec2& v)
    {
        this->x += v.x;
        this->y += v.y;
        return *this;
    }

    inline Vec2 operator-(const Vec2& v) const
    {
        return { x - v.x, y - v.y };
    }

    inline Vec2& operator-=(const Vec2& v)
    {
        this->x -= v.x;
        this->y -= v.y;
        return *this;
    }

    inline Vec2 operator-() const
    {
        return { -x, -y };
    }

    inline Vec2 operator*(const Vec2& v) const
    {
        return { x * v.x, y * v.y };
    }

    inline Vec2 operator*(float s) const
    {
        return { x * s, y * s };
    }

    inline Vec2& operator*=(float s)
    {
        this->x *= s;
        this->y *= s;
        return *this;
    }

    inline Vec2 operator/(float s) const
    {
        return { x / s, y / s };
    }

    inline Vec2& operator/=(float s)
    {
        this->x /= s;
        this->y /= s;
        return *this;
    }

    inline Vec2 operator/(Vec2 s) const
    {
        return { x / s.x, y / s.y };
    }

    inline Vec2& operator/=(Vec2 s)
    {
        this->x /= s.x;
        this->y /= s.y;
        return *this;
    }

    inline bool operator<(const Vec2& v) const
    {
        return x < v.x&& y < v.y;
    }

    inline bool operator>(const Vec2& v) const
    {
        return x > v.x && y > v.y;
    }

    inline bool operator==(const Vec2& v) const
    {
        return isEqual(x, v.x) && isEqual(y, v.y);
    }

    inline bool operator!=(const Vec2& v) const
    {
        return !isEqual(x, v.x) || !isEqual(y, v.y);
    }

    inline static Vec2 add(const Vec2& v1, const Vec2& v2)
    {
        return { v1.x + v2.x, v1.y + v2.y };
    }

    inline static float dot(const Vec2& v1, const Vec2& v2)
    {
        return v1.x * v2.x + v1.y * v2.y;
    }

    static float angle(const Vec2& v1, const Vec2& v2);

    inline float lineEquation(const Vec2& p1, const Vec2& p2) const
    {
        return (p1.x - p2.x) * (this->y - p2.y) - (p1.y - p2.y) * (this->x - p2.x);
    }

    inline bool isInLine(const Vec2& v1, const Vec2& v2) const
    {
        return isEqual(lineEquation(v1, v2), 0.0f);
    }

};


inline Vec2 operator*(float s, const Vec2& v)
{
    return { s * v.x, s * v.y };
}

